using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using NFreeMarker.Log;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Core
{
    /**
     * object that represents the runtime environment during template processing.
     * For every invocation of a <tt>Template.process()</tt> method, a new instance
     * of this object is created, and then discarded when <tt>process()</tt> returns.
     * This object stores the set of temporary variables created by the template,
     * the value of settings set by the template, the reference to the data model root,
     * etc. Everything that is needed to fulfill the template processing job.
     *
     * <p>Data models that need to access the <tt>Environment</tt>
     * object that represents the template processing on the current thread can use
     * the {@link #getCurrentEnvironment()} method.
     *
     * <p>If you need to modify or read this object before or after the <tt>process</tt>
     * call, use {@link Template#createProcessingEnvironment(object rootMap, Writer out, ObjectWrapper wrapper)}
     *
     * @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
     * @author Attila Szegedi
     */

    public sealed class Environment : Configurable
    {
        [ThreadStatic]
        private static Environment _threadEnv;

        private static readonly Logger Logger = Logger.GetLogger("freemarker.runtime");
        private static readonly Logger AttemptLogger = Logger.GetLogger("freemarker.runtime.attempt");

        private static readonly Dictionary<NumberFormatKey, string> LocalizedNumberFormats = new Dictionary<NumberFormatKey, string>();
        private static readonly Dictionary<DateFormatKey, string> LocalizedDateFormats = new Dictionary<DateFormatKey, string>();

        private const string CNumberFormat = "0.################";

        private readonly ITemplateHashModel _rootDataModel;
        private readonly Stack<TemplateElement> _elementStack = new Stack<TemplateElement>();
        private readonly List<string> _recoveredErrorStack = new List<string>();

        private string _numberFormat;
        private Dictionary<string, string> _numberFormats;

        private string _timeFormat, _dateFormat, _dateTimeFormat;
        private Dictionary<string, string>[] _dateFormats;

        private TextWriter _out;
        private Macro.Context _currentMacroContext;
        private List<ILocalContext> _localContextStack; 
        private readonly Namespace _mainNamespace;
        private Namespace _currentNamespace;
        private readonly Namespace _globalNamespace;
        private Dictionary<string, Namespace> _loadedLibs;

        private Exception _lastThrowable;

        private ITemplateModel _lastReturnValue;
        private readonly Dictionary<Macro, Namespace> _macroToNamespaceLookup = new Dictionary<Macro, Namespace>();

        private ITemplateNodeModel _currentVisitorNode;
        private ITemplateSequenceModel _nodeNamespaces;
        // Things we keep track of for the fallback mechanism.
        private int _nodeNamespaceIndex;
        private string _currentNodeName, _currentNodeNs;

        private Encoding _cachedUrlEscapingCharset;
        private bool _urlEscapingCharsetCached;

        /**
         * Retrieves the environment object associated with the current
         * thread. Data model implementations that need access to the
         * environment can call this method to obtain the environment object
         * that represents the template processing that is currently running
         * on the current thread.
         */

        public static Environment GetCurrentEnvironment()
        {
            return _threadEnv;
        }

        public Environment(Template.Template template, ITemplateHashModel rootDataModel, TextWriter output)
            : base(template)
        {
            _globalNamespace = new Namespace(this, null);
            _currentNamespace = _mainNamespace = new Namespace(this, template);
            _out = output;
            _rootDataModel = rootDataModel;
            ImportMacros(template);
        }

        /**
         * Retrieves the currently processed template.
         */

        public Template.Template GetTemplate()
        {
            return (Template.Template) GetParent();
        }

        /**
         * Deletes cached values that meant to be valid only during a single
         * template execution. 
         */
        private void ClearCachedValues()
        {
            _numberFormats = null;
            _numberFormat = null;
            _dateFormats = null;
            _cachedUrlEscapingCharset = null;
            _urlEscapingCharsetCached = false;
        }

        /**
         * Processes the template to which this environment belongs.
         */
        public void Process()
        {
            Environment savedEnv = _threadEnv;
            _threadEnv = this;
            try
            {
                // Cached values from a previous execution are possibly outdated.
                ClearCachedValues();
                try
                {
                    DoAutoImportsAndIncludes(this);
                    Visit(GetTemplate().GetRootTreeNode());
                    // Do not flush if there was an exception.
                    _out.Flush();
                }
                finally
                {
                    // It's just to allow the GC to free memory...
                    ClearCachedValues();
                }
            }
            finally
            {
                _threadEnv = savedEnv;
            }
        }

        /**
         * "Visit" the template element.
         */
        internal void Visit(TemplateElement element)
        {
            PushElement(element);
            try
            {
                element.Accept(this);
            }
            catch (TemplateException te)
            {
                HandleTemplateException(te);
            }
            finally
            {
                PopElement();
            }
        }

        private static readonly ITemplateModel[] NoOutArgs = new ITemplateModel[0];

        public void Visit(
            TemplateElement element,
            ITemplateDirectiveModel directiveModel,
            IDictionary<string, ITemplateModel> args,
            List<string> bodyParameterNames)
        {
            ITemplateDirectiveBody nested;
            if(element == null)
            {
                nested = null;
            }
            else
            {
                //nested = new TemplateDirectiveBody() {
                //    public void render(Writer newOut) throws TemplateException, IOException {
                //        Writer prevOut = out;
                //        out = newOut;
                //        try {
                //            Environment.this.visit(element);
                //        }
                //        finally {
                //            out = prevOut;
                //        }
                //    }
                //};
                throw new NotImplementedException();
            }
            ITemplateModel[] outArgs;
            if(bodyParameterNames == null || bodyParameterNames.Count == 0)
            {
                outArgs = NoOutArgs;
            }
            else
            {
                outArgs = new ITemplateModel[bodyParameterNames.Count];
            }
            if (outArgs.Length > 0)
            {
                //PushLocalContext(new LocalContext() {
                //    public ITemplateModel GetLocalVariable(string name)
                //    {
                //        int index = bodyParameterNames.IndexOf(name);
                //        return index != -1 ? outArgs[index] : null;
                //    }

                //    public Collection GetLocalVariableNames() {
                //        return bodyParameterNames;
                //    }
                //});
                throw new NotImplementedException();
            }
            try
            {
                directiveModel.Execute(this, args, outArgs, nested);
            }
            finally
            {
                if (outArgs.Length > 0)
                {
                    PopLocalContext();
                }
            }
        }

        /**
         * "Visit" the template element, passing the output
         * through a TemplateTransformModel
         * @param element the element to visit through a transform
         * @param transform the transform to pass the element output
         * through
         * @param args optional arguments fed to the transform
         */

        internal void Visit(
            TemplateElement element,
            ITemplateTransformModel transform,
            IDictionary<string, ITemplateModel> args)
        {
            try
            {
                TextWriter tw = transform.GetWriter(_out, args) ?? EmptyBodyWriter;
                ITransformControl tc = tw is ITransformControl
                    ? (ITransformControl) tw
                    : null;

                TextWriter prevOut = _out;
                _out = tw;
                try
                {
                    if (tc == null || tc.OnStart() != TransformControl.SkipBody)
                    {
                        do
                        {
                            if (element != null)
                            {
                                Visit(element);
                            }
                        } while (tc != null && tc.AfterBody() == TransformControl.RepeatEvaluation);
                    }
                }
                catch (Exception t)
                {
                    try
                    {
                        if (tc != null)
                        {
                            tc.OnError(t);
                        }
                        else
                        {
                            throw t;
                        }
                    }
                    catch (TemplateException te)
                    {
                        throw;
                    }
                    catch (IOException ioe) {
                        throw;
                    }
                    //catch(RuntimeException re) {
                    //    throw;
                    //}
                    //catch(Error e) {
                    //    throw e;
                    //}
                    catch(Exception e) {
                        //throw new UndeclaredThrowableException(e);
                        throw new NotImplementedException();
                    }
                }
                finally
                {
                    _out = prevOut;
                    tw.Close();
                }
            }
            catch (TemplateException te)
            {
                HandleTemplateException(te);
            }
        }

        /**
         * Visit a block using buffering/recovery
         */

        internal void Visit(TemplateElement attemptBlock, TemplateElement recoveryBlock)
        {
             TextWriter prevOut = _out;
             var sw = new StringWriter();
             _out = sw;
             TemplateException thrownException = null;
             try
             {
                 Visit(attemptBlock);
             }
             catch (TemplateException te)
             {
                 thrownException = te;
             }
             finally {
                 _out = prevOut;
             }
             if (thrownException != null)
             {
                 if (AttemptLogger.IsDebugEnabled())
                 {
                     AttemptLogger.Debug("Error in attempt block " +
                             attemptBlock.GetStartLocation(), thrownException);
                 }
                 try
                 {
                     _recoveredErrorStack.Add(thrownException.Message);
                     Visit(recoveryBlock);
                 }
                 finally
                 {
                     _recoveredErrorStack.RemoveAt(_recoveredErrorStack.Count - 1);
                 }
             }
             else
             {
                 _out.Write(sw.ToString());
             }
        }

        internal string GetCurrentRecoveredErrorMesssage()
        {
            if (_recoveredErrorStack.Count == 0)
            {
                throw new TemplateException(
                    ".error is not available outside of a <#recover> block", this);
            }
            return _recoveredErrorStack[_recoveredErrorStack.Count - 1];
        }

        internal void Visit(BodyInstruction.Context bctxt)
        {
            Macro.Context invokingMacroContext = GetCurrentMacroContext();
            List<ILocalContext> prevLocalContextStack = _localContextStack;
            TemplateElement body = invokingMacroContext.Body;
            if (body != null)
            {
                _currentMacroContext = invokingMacroContext.PrevMacroContext;
                _currentNamespace = invokingMacroContext.BodyNamespace;
                Configurable prevParent = GetParent();
                SetParent(_currentNamespace.GetTemplate());
                _localContextStack = invokingMacroContext.PrevLocalContextStack;
                if (invokingMacroContext.BodyParameterNames != null)
                {
                    PushLocalContext(bctxt);
                }
                try
                {
                    Visit(body);
                }
                finally
                {
                    if (invokingMacroContext.BodyParameterNames != null)
                    {
                        PopLocalContext();
                    }
                    _currentMacroContext = invokingMacroContext;
                    _currentNamespace = GetMacroNamespace(invokingMacroContext.GetMacro());
                    SetParent(prevParent);
                    _localContextStack = prevLocalContextStack;
                }
            }
        }

        /**
         * "visit" an IteratorBlock
         */

        internal void Visit(IteratorBlock.Context ictxt)
        {
            PushLocalContext(ictxt);
            try
            {
                ictxt.RunLoop(this);
            }
            catch (BreakInstruction.Break)
            {
            }
            catch (TemplateException te)
            {
                HandleTemplateException(te);
            }
            finally
            {
                PopLocalContext();
            }
        }

        /**
         * "Visit" A TemplateNodeModel
         */
        internal void Visit(ITemplateNodeModel node, ITemplateSequenceModel namespaces) 
        {
            if (_nodeNamespaces == null)
            {
                var ss = new SimpleSequence(1);
                ss.Add(_currentNamespace);
                _nodeNamespaces = ss;
            }
            int prevNodeNamespaceIndex = _nodeNamespaceIndex;
            string prevNodeName = _currentNodeName;
            string prevNodeNs = _currentNodeNs;
            ITemplateSequenceModel prevNodeNamespaces = _nodeNamespaces;
            ITemplateNodeModel prevVisitorNode = _currentVisitorNode;
            _currentVisitorNode = node;
            if (namespaces != null)
            {
                _nodeNamespaces = namespaces;
            }
            try
            {
                ITemplateModel macroOrTransform = GetNodeProcessor(node);
                if (macroOrTransform is Macro)
                {
                    Visit((Macro) macroOrTransform, null, null, null, null);
                }
                else if (macroOrTransform is ITemplateTransformModel)
                {
                    Visit(null, (ITemplateTransformModel) macroOrTransform, null); 
                }
                else
                {
                    string nodeType = node.GetNodeType();
                    if (nodeType != null)
                    {
                        // If the node's type is 'text', we just output it.
                        if ((nodeType.Equals("text") && node is ITemplateScalarModel)) 
                        {
                               _out.Write(((ITemplateScalarModel) node).GetAsString());
                        }
                        else if (nodeType.Equals("document"))
                        {
                            Recurse(node, namespaces);
                        }
                        // We complain here, unless the node's type is 'pi', or "comment" or "document_type", in which case
                        // we just ignore it.
                        else if (!nodeType.Equals("pi") 
                             && !nodeType.Equals("comment") 
                             && !nodeType.Equals("document_type")) 
                        {
                            string nsBit = "";
                            string ns = node.GetNodeNamespace();
                            if (ns != null)
                            {
                                if (ns.Length > 0)
                                {
                                    nsBit = " and namespace " + ns;
                                }
                                else
                                {
                                    nsBit = " and no namespace";
                                }
                            }
                            throw new TemplateException("No macro or transform defined for node named "  
                                        + node.GetNodeName() + nsBit
                                        + ", and there is no fallback handler called @" + nodeType + " either.",
                                        this);
                        }
                    }
                    else
                    {
                        string nsBit = "";
                        string ns = node.GetNodeNamespace();
                        if (ns != null)
                        {
                            if (ns.Length > 0)
                            {
                                nsBit = " and namespace " + ns;
                            }
                            else
                            {
                                nsBit = " and no namespace";
                            }
                        }
                        throw new TemplateException("No macro or transform defined for node with name " 
                                    + node.GetNodeName() + nsBit 
                                    + ", and there is no macro or transform called @default either.",
                                    this);
                    }
                }
            } 
            finally
            {
                _currentVisitorNode = prevVisitorNode;
                _nodeNamespaceIndex = prevNodeNamespaceIndex;
                _currentNodeName = prevNodeName;
                _currentNodeNs = prevNodeNs;
                _nodeNamespaces = prevNodeNamespaces;
            }
        }

        internal void Fallback()
        {
            ITemplateModel macroOrTransform = GetNodeProcessor(_currentNodeName, _currentNodeNs, _nodeNamespaceIndex);
            if (macroOrTransform is Macro)
            {
                Visit((Macro)macroOrTransform, null, null, null, null);
            }
            else if (macroOrTransform is ITemplateTransformModel)
            {
                Visit(null, (ITemplateTransformModel) macroOrTransform, null);
            }
        }

        /**
         * "visit" a macro.
         */
        internal void Visit(
            Macro macro, 
            Dictionary<string, Expression> namedArgs, 
            List<Expression> positionalArgs, 
            List<string> bodyParameterNames,
            TemplateElement nestedBlock)
        {
            if (macro == Macro.DoNothingMacro)
            {
                return;
            }
            PushElement(macro);
            try
            {
                Macro.Context previousMacroContext = _currentMacroContext;
                var mc = new Macro.Context(macro, this, nestedBlock, bodyParameterNames);
                string catchAll = macro.GetCatchAll();
                ITemplateModel unknownVars = null;
                if (namedArgs != null)
                {
                    if (catchAll != null)
                    {
                        unknownVars = new SimpleHash();
                    }
                    foreach (KeyValuePair<string, Expression> namedArg in namedArgs)
                    {
                        string varName = namedArg.Key;
                        bool hasVar = macro.HasArgNamed(varName);
                        if (hasVar || catchAll != null)
                        {
                            Expression arg = namedArg.Value;
                            ITemplateModel value = arg.GetAsTemplateModel(this);
                            if (hasVar)
                            {
                                mc.SetLocalVar(varName, value);
                            }
                            else
                            {
                                ((SimpleHash) unknownVars).Put(varName, value);
                            }
                        }
                        else
                        {
                            string msg = "Macro " + macro.GetName() + " has no such argument: " + varName;
                            throw new TemplateException(msg, this);
                        }
                    }
                }
                else if (positionalArgs != null)
                {
                    if (catchAll != null)
                    {
                        unknownVars = new SimpleSequence();
                    }
                    string[] argumentNames = macro.GetArgumentNamesInternal();
                    int size = positionalArgs.Count;
                    if (argumentNames.Length < size && catchAll == null)
                    {
                        throw new TemplateException("Macro " + macro.GetName() 
                          + " only accepts " + argumentNames.Length + " parameters.", this);
                    }
                    for (int i = 0; i < size; i++)
                    {
                        Expression argExp = positionalArgs[i];
                        ITemplateModel argModel = argExp.GetAsTemplateModel(this);
                        //try
                        //{
                            if (i < argumentNames.Length)
                            {
                                string argName = argumentNames[i];
                                mc.SetLocalVar(argName, argModel);
                            }
                            else
                            {
                                ((SimpleSequence) unknownVars).Add(argModel);
                            }
                        //}
                        //catch (RuntimeException re)
                        //{
                        //    throw new TemplateException(re, this);
                        //}
                        throw new NotImplementedException();
                    }
                }
                if (catchAll != null)
                {
                    mc.SetLocalVar(catchAll, unknownVars);
                }
                List<ILocalContext> prevLocalContextStack = _localContextStack;
                _localContextStack = null;
                Namespace prevNamespace = _currentNamespace;
                Configurable prevParent = GetParent();
                _currentNamespace = _macroToNamespaceLookup.GetValueOrDefault(macro);
                _currentMacroContext = mc;
                try
                {
                    mc.RunMacro(this);
                }
                catch (ReturnInstruction.Return )
                {
                }
                catch (TemplateException te)
                {
                    HandleTemplateException(te);
                }
                finally
                {
                    _currentMacroContext = previousMacroContext;
                    _localContextStack = prevLocalContextStack;
                    _currentNamespace = prevNamespace;
                    SetParent(prevParent);
                }
            }
            finally
            {
                PopElement();
            }
        }

        internal void VisitMacroDef(Macro macro)
        {
            _macroToNamespaceLookup[macro] = _currentNamespace;
            _currentNamespace.Put(macro.GetName(), macro);
        }

        internal Namespace GetMacroNamespace(Macro macro)
        {
            return _macroToNamespaceLookup[macro];
        }

        internal void Recurse(ITemplateNodeModel node, ITemplateSequenceModel namespaces)
        {
            if (node == null)
            {
                node = GetCurrentVisitorNode();
                if (node == null)
                {
                    throw new TemplateModelException(
                        "The target node of recursion is missing or null.");
                }
            }
            ITemplateSequenceModel children = node.GetChildNodes();
            if (children == null) return;
            for (int i = 0; i < children.Size(); i++)
            {
                var child = (ITemplateNodeModel) children.Get(i);
                if (child != null)
                {
                    Visit(child, namespaces);
                }
            }
        }

        internal Macro.Context GetCurrentMacroContext()
        {
            return _currentMacroContext;
        }

        private void HandleTemplateException(TemplateException te)
        {
            // Logic to prevent double-handling of the exception in
            // nested visit() calls.
            if (_lastThrowable == te)
            {
                throw te;
            }
            _lastThrowable = te;

            // Log the exception
            if (Logger.IsErrorEnabled())
            {
                Logger.Error(te.Message, te);
            }

            // Stop exception is not passed to the handler, but
            // explicitly rethrown.
            if (te is StopException)
            {
                throw te;
            }

            // Finally, pass the exception to the handler
            GetTemplateExceptionHandler().HandleTemplateException(te, this, _out);
        }

        public override void SetTemplateExceptionHandler(ITemplateExceptionHandler templateExceptionHandler)
        {
            base.SetTemplateExceptionHandler(templateExceptionHandler);
            _lastThrowable = null;
        }

        //public void setLocale(Locale locale) {
        //    super.setLocale(locale);
        //    // Clear local format cache
        //    numberFormats = null;
        //    numberFormat = null;

        //    dateFormats = null;
        //    timeFormat = dateFormat = dateTimeFormat = null;
        //}

        //public void setTimeZone(TimeZone timeZone) {
        //    super.setTimeZone(timeZone);
        //    // Clear local date format cache
        //    dateFormats = null;
        //    timeFormat = dateFormat = dateTimeFormat = null;
        //}

        public override void SetUrlEscapingCharset(Encoding urlEscapingCharset)
        {
            _urlEscapingCharsetCached = false;
            base.SetUrlEscapingCharset(urlEscapingCharset);
        }

        /*
         * Note that altough it is not allowed to set this setting with the
         * <tt>setting</tt> directive, it still must be allowed to set it from Java
         * code while the template executes, since some frameworks allow templates
         * to actually change the output encoding on-the-fly.
         */
        public override void SetOutputEncoding(Encoding outputEncoding)
        {
            _urlEscapingCharsetCached = false;
            base.SetOutputEncoding(outputEncoding);
        }

        /**
         * Returns the name of the charset that should be used for URL encoding.
         * This will be <code>null</code> if the information is not available.
         * The function caches the return value, so it is quick to call it
         * repeately. 
         */
        internal Encoding GetEffectiveUrlEscapingCharset()
        {
            if (!_urlEscapingCharsetCached)
            {
                _cachedUrlEscapingCharset = GetUrlEscapingCharset();
                if (_cachedUrlEscapingCharset == null)
                {
                    _cachedUrlEscapingCharset = GetOutputEncoding();
                }
                _urlEscapingCharsetCached = true;
            }
            return _cachedUrlEscapingCharset;
        }

        public void SetOut(TextWriter output)
        {
            _out = output;
        }

        public TextWriter GetOut()
        {
            return _out;
        }

        internal string FormatNumber(Number number)
        {
            if (_numberFormat == null)
            {
                _numberFormat = GetNumberFormatObject(GetNumberFormat());
            }

            var numberFormatInfo = GetLocale().NumberFormat;

            // Convert the number to a string. For example, 1 can result in "1.00".
            string str = number.DecimalValue().ToString(_numberFormat, numberFormatInfo);

            // Remove the potentially trailing zeroes. For example, "1.00" will result in "1.".
            str = str.TrimEnd(numberFormatInfo.NativeDigits[0][0]);

            // Remove the potentially trailing decimal separator. For example, "1." will result in "1".
            if (str.EndsWith(numberFormatInfo.NumberDecimalSeparator))
            {
                str = str.Substring(0, str.Length - numberFormatInfo.NumberDecimalSeparator.Length);
            }

            return str;
        }

        public override void SetNumberFormat(string formatName)
        {
            base.SetNumberFormat(formatName);
            _numberFormat = null;
        }

        internal string FormatDate(DateTimeOffset date, int type)
        {
            string df = GetDateFormatObject(type);
            if(df == null)
            {
                throw new TemplateModelException("Can't convert the date to string, because it is not known which parts of the date variable are in use. Use ?date, ?time or ?datetime built-in, or ?string.<format> or ?string(format) built-in with this date.");
            }
            return date.ToString(df, GetLocale().DateTimeFormat);
        }

        public override void SetTimeFormat(string formatName)
        {
            base.SetTimeFormat(formatName);
            _timeFormat = null;
        }

        public override void SetDateFormat(string formatName)
        {
            base.SetDateFormat(formatName);
            _dateFormat = null;
        }

        public override void SetDateTimeFormat(string formatName)
        {
            base.SetDateTimeFormat(formatName);
            _dateTimeFormat = null;
        }

        public Configuration GetConfiguration()
        {
            return GetTemplate().GetConfiguration();
        }

        internal ITemplateModel GetLastReturnValue()
        {
            return _lastReturnValue;
        }

        internal void SetLastReturnValue(ITemplateModel lastReturnValue)
        {
            _lastReturnValue = lastReturnValue;
        }

        private void ClearLastReturnValue()
        {
            _lastReturnValue = null;
        }

        internal string GetNumberFormatObject(string pattern)
        {
            if (_numberFormats == null)
            {
                _numberFormats = new Dictionary<string, string>();
            }

            string format = _numberFormats.GetValueOrDefault(pattern);
            if(format != null)
            {
                return format;
            }

            // Get format from global format cache
            lock (LocalizedNumberFormats)
            {
                CultureInfo locale = GetLocale();
                var fk = new NumberFormatKey(pattern, locale);
                format = LocalizedNumberFormats.GetValueOrDefault(fk);
                if (format == null)
                {
                    // Add format to global format cache. Note this is
                    // globally done once per locale per pattern.
                    switch (pattern)
                    {
                        case "number":
                            format = "N";
                            break;
                        case "currency":
                            format = "C";
                            break;
                        case "percent":
                            format = "P";
                            break;
                        case "computer":
                            format = GetCNumberFormat();
                            break;
                        default:
                            //format = new DecimalFormat(pattern, new DecimalFormatSymbols(getLocale()));
                            //break;
                            throw new NotImplementedException();
                    }
                    LocalizedNumberFormats.Add(fk, format);
                }
            }

            // Clone it and store the clone in the local cache
            // TODO: format = (NumberFormat) format.clone();
            _numberFormats.Add(pattern, format);
            return format;
        }

        internal string GetDateFormatObject(int dateType)
        {
            switch (dateType)
            {
                case TemplateDateModel.Unknown:
                    {
                        return null;
                    }
                case TemplateDateModel.Time:
                    {
                        if (_timeFormat == null)
                        {
                            _timeFormat = GetDateFormatObject(dateType, GetTimeFormat());
                        }
                        return _timeFormat;
                    }
                case TemplateDateModel.Date:
                    {
                        if (_dateFormat == null)
                        {
                            _dateFormat = GetDateFormatObject(dateType, GetDateFormat());
                        }
                        return _dateFormat;
                    }
                case TemplateDateModel.DateTime:
                    {
                        if (_dateTimeFormat == null)
                        {
                            _dateTimeFormat = GetDateFormatObject(dateType, GetDateTimeFormat());
                        }
                        return _dateTimeFormat;
                    }
                default:
                    {
                        throw new TemplateModelException("Unrecognized date type " + dateType);
                    }
            }
        }

        internal string GetDateFormatObject(int dateType, string pattern)
        {
            if (_dateFormats == null)
            {
                _dateFormats = new Dictionary<string, string>[4];
                _dateFormats[TemplateDateModel.Unknown] = new Dictionary<string, string>();
                _dateFormats[TemplateDateModel.Time] = new Dictionary<string, string>();
                _dateFormats[TemplateDateModel.Date] = new Dictionary<string, string>();
                _dateFormats[TemplateDateModel.DateTime] = new Dictionary<string, string>();
            }
            Dictionary<string, string> typedDateFormat = _dateFormats[dateType];

            string format = typedDateFormat.GetValueOrDefault(pattern);
            if (format != null)
            {
                return format;
            }

            // Get format from global format cache
            lock (LocalizedDateFormats)
            {
                CultureInfo locale = GetLocale();
                TimeZoneInfo timeZone = GetTimeZone();
                var fk = new DateFormatKey(dateType, pattern, locale, timeZone);
                format = LocalizedDateFormats.GetValueOrDefault(fk);
                if (format == null)
                {
                    // Add format to global format cache. Note this is
                    // globally done once per locale per pattern.
                    // TODO: StringTokenizer tok = new StringTokenizer(pattern, "_");
                    //int style = tok.hasMoreTokens() ? parseDateStyleToken(tok.nextToken()) : DateFormat.DEFAULT;
                    //if (style != -1)
                    //{

                    if (pattern.Length == 0) // TODO: Was the above
                    {
                        switch(dateType)
                        {
                            case TemplateDateModel.Unknown:
                            {
                                throw new TemplateModelException(
                                    "Can't convert the date to string using a " +
                                    "built-in format, because it is not known which " +
                                    "parts of the date variable are in use. Use " +
                                    "?date, ?time or ?datetime built-in, or " +
                                    "?string.<format> or ?string(<format>) built-in "+
                                    "with explicit formatting pattern with this date.");
                            }
                            case TemplateDateModel.Time:
                            {
                                format = "T";
                                break;
                            }
                            case TemplateDateModel.Date:
                            {
                                format = "D";
                                break;
                            }
                            case TemplateDateModel.DateTime:
                            {
                                // TODO: Was int timestyle = tok.hasMoreTokens() ? parseDateStyleToken(tok.nextToken()) : style;
                                //if(timestyle != -1) {
                                //    format = DateFormat.getDateTimeInstance(style, timestyle, locale);
                                //}

                                format = "F";
                                break;
                            }
                        }
                    }
                    if (format == null)
                    {
                        format = pattern;
                    }
                    // TODO: format.setTimeZone(timeZone);
                    LocalizedDateFormats.Add(fk, format);
                }
            }

            // Clone it and store the clone in the local cache
            // TODO: format = (DateFormat)format.clone();
            typedDateFormat.Add(pattern, format);

            return format;
        }

        //int parseDateStyleToken(string token) {
        //    if("short".equals(token)) {
        //        return DateFormat.SHORT;
        //    }
        //    if("medium".equals(token)) {
        //        return DateFormat.MEDIUM;
        //    }
        //    if("long".equals(token)) {
        //        return DateFormat.LONG;
        //    }
        //    if("full".equals(token)) {
        //        return DateFormat.FULL;
        //    }
        //    return -1;
        //}

        /**
         * Returns the {@link NumberFormat} used for the <tt>c</tt> built-in.
         * This is always US English <code>"0.################"</code>, without
         * grouping and without superfluous decimal separator.
         */
        public string GetCNumberFormat()
        {
            return CNumberFormat;
        }

        internal ITemplateTransformModel GetTransform(Expression exp)
        {
            ITemplateTransformModel ttm = null;
            ITemplateModel tm = exp.GetAsTemplateModel(this);
            if (tm is ITemplateTransformModel)
            {
                ttm = (ITemplateTransformModel)tm;
            }
            else if (exp is Identifier)
            {
                tm = GetConfiguration().GetSharedVariable(exp.ToString());
                if (tm is ITemplateTransformModel)
                {
                    ttm = (ITemplateTransformModel) tm;
                }
            }
            return ttm;
        }

        /**
         * Returns the loop or macro local variable corresponding to this
         * variable name. Possibly null.
         * (Note that the misnomer is kept for backward compatibility: loop variables
         * are not local variables according to our terminology.)
         */
        public ITemplateModel GetLocalVariable(string name)
        {
            if (_localContextStack != null) {
                for (int i = _localContextStack.Count-1; i>=0; i--) {
                    ILocalContext lc = _localContextStack[i];
                    ITemplateModel tm = lc.GetLocalVariable(name);
                    if (tm != null) {
                        return tm;
                    }
                }
            }
            return _currentMacroContext == null ? null : _currentMacroContext.GetLocalVariable(name);
        }

        /**
         * Returns the variable that is visible in this context.
         * This is the correspondent to an FTL top-level variable reading expression.
         * That is, it tries to find the the variable in this order:
         * <ol>
         *   <li>An loop variable (if we're in a loop or user defined directive body) such as foo_has_next
         *   <li>A local variable (if we're in a macro)
         *   <li>A variable defined in the current namespace (say, via &lt;#assign ...&gt;)
         *   <li>A variable defined globally (say, via &lt;#global ....&gt;)
         *   <li>Variable in the data model:
         *     <ol>
         *       <li>A variable in the root hash that was exposed to this
                     rendering environment in the Template.process(...) call
         *       <li>A shared variable set in the configuration via a call to Configuration.setSharedVariable(...)
         *     </ol>
         *   </li>
         * </ol>
         */
        public ITemplateModel GetVariable(string name)
        {
            ITemplateModel result = GetLocalVariable(name);
            if (result == null)
            {
                result = _currentNamespace.Get(name);
            }
            if (result == null)
            {
                result = GetGlobalVariable(name);
            }
            return result;
        }

        /**
         * Returns the globally visible variable of the given name (or null).
         * This is correspondent to FTL <code>.globals.<i>name</i></code>.
         * This will first look at variables that were assigned globally via:
         * &lt;#global ...&gt; and then at the data model exposed to the template.
         */
        public ITemplateModel GetGlobalVariable(string name)
        {
            ITemplateModel result = _globalNamespace.Get(name);
            if (result == null) {
                result = _rootDataModel.Get(name);
            }
            if (result == null) {
                result = GetConfiguration().GetSharedVariable(name);
            }
            return result;
        }

        /**
         * Sets a variable that is visible globally.
         * This is correspondent to FTL <code><#global <i>name</i>=<i>model</i>></code>.
         * This can be considered a convenient shorthand for:
         * getGlobalNamespace().put(name, model)
         */
        public void SetGlobalVariable(string name, ITemplateModel model)
        {
            _globalNamespace.Put(name, model);
        }

        /**
         * Sets a variable in the current namespace.
         * This is correspondent to FTL <code><#assign <i>name</i>=<i>model</i>></code>.
         * This can be considered a convenient shorthand for:
         * getCurrentNamespace().put(name, model)
         */

        public void SetVariable(string name, ITemplateModel model)
        {
            _currentNamespace.Put(name, model);
        }

        /**
         * Sets a local variable (one effective only during a macro invocation).
         * This is correspondent to FTL <code><#local <i>name</i>=<i>model</i>></code>.
         * @param name the identifier of the variable
         * @param model the value of the variable.
         * @throws IllegalStateException if the environment is not executing a
         * macro body.
         */

        public void SetLocalVariable(string name, ITemplateModel model)
        {
            if (_currentMacroContext == null)
            {
                throw new InvalidOperationException("Not executing macro body");
            }
            _currentMacroContext.SetLocalVar(name, model);
        }

        /**
         * Returns a set of variable names that are known at the time of call. This
         * includes names of all shared variables in the {@link Configuration},
         * names of all global variables that were assigned during the template processing,
         * names of all variables in the current name-space, names of all local variables
         * and loop variables. If the passed root data model implements the
         * {@link TemplateHashModelEx} interface, then all names it retrieves through a call to
         * {@link TemplateHashModelEx#keys()} method are returned as well.
         * The method returns a new Set object on each call that is completely
         * disconnected from the Environment. That is, modifying the set will have
         * no effect on the Environment object.
         */
        public HashSet<string> GetKnownVariableNames()
        {
            // shared vars.
            HashSet<string> set = GetConfiguration().GetSharedVariableNames();
            // root hash
            if (_rootDataModel is ITemplateHashModelEx)
            {
                ITemplateModelIterator rootNames =
                    ((ITemplateHashModelEx) _rootDataModel).Keys().Iterator();
                while (rootNames.HasNext())
                {
                    set.Add(((ITemplateScalarModel) rootNames.Next()).GetAsString());
                }
            }
            // globals
            for (ITemplateModelIterator tmi = _globalNamespace.Keys().Iterator(); tmi.HasNext();)
            {
                set.Add(((ITemplateScalarModel) tmi.Next()).GetAsString());
            }
            // current name-space
            for (ITemplateModelIterator tmi = _currentNamespace.Keys().Iterator(); tmi.HasNext();)
            {
                set.Add(((ITemplateScalarModel) tmi.Next()).GetAsString());
            }
            // locals and loop vars
            if (_currentMacroContext != null)
            {
                foreach (string variableName in _currentMacroContext.GetLocalVariableNames())
                {
                    set.Add(variableName);
                }
            }
            if (_localContextStack != null)
            {
                IEnumerable<string> variableNames = Enumerable
                    .Reverse(_localContextStack)
                    .SelectMany(lc => lc.GetLocalVariableNames());
                foreach (string variableName in variableNames)
                {
                    set.Add(variableName);
                }
            }
            return set;
        }

        /**
         * Outputs the instruction stack. Useful for debugging.
         * {@link TemplateException}s incorporate this information in their stack
         * traces.
         */
        public void OutputInstructionStack(TextWriter pw)
        {
            pw.WriteLine("----------");
            IEnumerator<TemplateElement> iter = _elementStack.Reverse().GetEnumerator();
            if (iter.MoveNext())
            {
                pw.Write("==> ");
                var prev = iter.Current;
                pw.Write(prev.GetDescription());
                pw.Write(" [");
                pw.Write(prev.GetStartLocation());
                pw.WriteLine("]");
            }
            while (iter.MoveNext())
            {
                var prev = iter.Current;
                if (prev is UnifiedCall || prev is Include)
                {
                    string location = prev.GetDescription() + " [" + prev.GetStartLocation() + "]";
                    if (!string.IsNullOrEmpty(location))
                    {
                        pw.Write(" in ");
                        pw.WriteLine(location);
                    }
                }
            }
            pw.WriteLine("----------");
            pw.Flush();
        }

        private void PushLocalContext(ILocalContext localContext)
        {
            if (_localContextStack == null)
            {
                _localContextStack = new List<ILocalContext>();
            }
            _localContextStack.Add(localContext);
        }

        private void PopLocalContext()
        {
            _localContextStack.RemoveAt(_localContextStack.Count - 1);
        }

        internal List<ILocalContext> GetLocalContextStack()
        {
            return _localContextStack;
        }

        /**
         * Returns the name-space for the name if exists, or null.
         * @param name the template path that you have used with the <code>import</code> directive
         *     or {@link #importLib(string, string)} call, in normalized form. That is, the path must be an absolute
         *     path, and it must not contain "/../" or "/./". The leading "/" is optional.
         */
        public Namespace GetNamespace(string name)
        {
            if (name.StartsWith("/")) name = name.Substring(1);
            return _loadedLibs != null
                ? _loadedLibs.GetValueOrDefault(name)
                : null;
        }

        /**
         * Returns the main name-space.
         * This is correspondent of FTL <code>.main</code> hash.
         */
        public Namespace GetMainNamespace()
        {
            return _mainNamespace;
        }

        /**
         * Returns the main name-space.
         * This is correspondent of FTL <code>.namespace</code> hash.
         */

        public Namespace GetCurrentNamespace()
        {
            return _currentNamespace;
        }

        /**
         * Returns a fictitious name-space that contains the globally visible variables
         * that were created in the template, but not the variables of the data-model.
         * There is no such thing in FTL; this strange method was added because of the
         * JSP taglib support, since this imaginary name-space contains the page-scope
         * attributes.
         */

        public Namespace GetGlobalNamespace()
        {
            return _globalNamespace;
        }

        public ITemplateHashModel GetDataModel()
        {
            //final TemplateHashModel result = new TemplateHashModel() {
            //    public boolean isEmpty() {
            //        return false;
            //    }

            //    public TemplateModel get(string key) throws TemplateModelException {
            //        TemplateModel value = rootDataModel.get(key);
            //        if (value == null) {
            //            value = getConfiguration().getSharedVariable(key);
            //        }
            //        return value;
            //    }
            //};

            //if (rootDataModel is TemplateHashModelEx) {
            //    return new TemplateHashModelEx() {
            //        public boolean isEmpty() throws TemplateModelException {
            //            return result.isEmpty();
            //        }
            //        public TemplateModel get(string key) throws TemplateModelException {
            //            return result.get(key);
            //        }

            //        //NB: The methods below do not take into account
            //        // configuration shared variables even though
            //        // the hash will return them, if only for BWC reasons
            //        public TemplateCollectionModel values() throws TemplateModelException {
            //            return ((TemplateHashModelEx) rootDataModel).values();
            //        }
            //        public TemplateCollectionModel keys() throws TemplateModelException {
            //            return ((TemplateHashModelEx) rootDataModel).keys();
            //        }
            //        public int size() throws TemplateModelException {
            //            return ((TemplateHashModelEx) rootDataModel).size();
            //        }
            //    };
            //}
            //return result;

            throw new NotImplementedException();
        }

        /**
         * Returns the read-only hash of globally visible variables.
         * This is the correspondent of FTL <code>.globals</code> hash.
         * That is, you see the variables created with
         * <code>&lt;#global ...></code>, and the variables of the data-model.
         * To create new global variables, use {@link #setGlobalVariable setGlobalVariable}.
         */

        public ITemplateHashModel GetGlobalVariables()
        {
            //return new TemplateHashModel() {
            //    public boolean isEmpty() {
            //        return false;
            //    }
            //    public TemplateModel get(string key) throws TemplateModelException {
            //        TemplateModel result = globalNamespace.get(key);
            //        if (result == null) {
            //            result = rootDataModel.get(key);
            //        }
            //        if (result == null) {
            //            result = getConfiguration().getSharedVariable(key);
            //        }
            //        return result;
            //    }
            //};

            throw new NotImplementedException();
        }

        private void PushElement(TemplateElement element)
        {
            _elementStack.Push(element);
        }

        private void PopElement()
        {
            _elementStack.Pop();
        }

        public ITemplateNodeModel GetCurrentVisitorNode()
        {
            return _currentVisitorNode;
        }

        /**
         * sets TemplateNodeModel as the current visitor node. <tt>.current_node</tt>
         */
        public void SetCurrentVisitorNode(ITemplateNodeModel node)
        {
            _currentVisitorNode = node;
        }

        ITemplateModel GetNodeProcessor(ITemplateNodeModel node)
        {
            string nodeName = node.GetNodeName();
            if (nodeName == null)
            {
                throw new TemplateException("Node name is null.", this);
            }
            ITemplateModel result = GetNodeProcessor(nodeName, node.GetNodeNamespace(), 0);
            if (result == null)
            {
                string type = node.GetNodeType();
                /* DD: Original version: */
                if (type == null)
                {
                    type = "default";
                }
                result = GetNodeProcessor("@" + type, null, 0);
                /* DD: Jonathan's non-BC version and IMHO otherwise wrong version:
                if (type != null) {
                    result = getNodeProcessor("@" + type, null, 0);
                }
                if (result == null) {
                    result = getNodeProcessor("@default", null, 0);
                }
                */
            }
            return result;    
        }

        private ITemplateModel GetNodeProcessor(string nodeName, string nsUri, int startIndex)
        {
            ITemplateModel result = null;
            int i;
            for (i = startIndex; i < _nodeNamespaces.Size(); i++)
            {
                Namespace ns = null;
                try
                {
                    ns = (Namespace) _nodeNamespaces.Get(i);
                }
                catch (InvalidCastException)
                {
                    throw new InvalidReferenceException("A using clause should contain a sequence of namespaces or strings that indicate the location of importable macro libraries.", this);
                }
                result = GetNodeProcessor(ns, nodeName, nsUri);
                if (result != null)
                {
                    break;
                }
            }
            if (result != null)
            {
                _nodeNamespaceIndex = i+1;
                _currentNodeName = nodeName;
                _currentNodeNs = nsUri;
            }
            return result;
        }

        private ITemplateModel GetNodeProcessor(Namespace ns, string localName, string nsUri)
        {
            ITemplateModel result = null;
            if (nsUri == null)
            {
                result = ns.Get(localName);
                if (!(result is Macro) && !(result is ITemplateTransformModel))
                {
                    result = null;
                }
            }
            else
            {
                Template.Template template = ns.GetTemplate();
                string prefix = template.GetPrefixForNamespace(nsUri);
                if (prefix == null)
                {
                    // The other template cannot handle this node
                    // since it has no prefix registered for the namespace
                    return null;
                }
                if (prefix.Length > 0)
                {
                    result = ns.Get(prefix + ":" + localName);
                    if (!(result is Macro) && !(result is ITemplateTransformModel))
                    {
                        result = null;
                    }
                }
                else
                {
                    if (nsUri.Length == 0)
                    {
                        result = ns.Get(Template.Template.NoNsPrefix + ":" + localName);
                        if (!(result is Macro) && !(result is ITemplateTransformModel))
                        {
                            result = null;
                        }
                    }
                    if (nsUri.Equals(template.GetDefaultNs()))
                    {
                        result = ns.Get(Template.Template.DefaultNamespacePrefix + ":" + localName);
                        if (!(result is Macro) && !(result is ITemplateTransformModel))
                        {
                            result = null;
                        }
                    }
                    if (result == null) {
                        result = ns.Get(localName);
                        if (!(result is Macro) && !(result is ITemplateTransformModel))
                        {
                            result = null;
                        }
                    }
                }
            }
            return result;
        }

        /**
         * Emulates <code>include</code> directive, except that <code>name</code> must be tempate
         * root relative.
         *
         * <p>It's the same as <code>include(getTemplateForInclusion(name, encoding, parse))</code>.
         * But, you may want to separately call these two methods, so you can determine the source of
         * exceptions more precisely, and thus achieve more intelligent error handling.
         *
         * @see #getTemplateForInclusion(string name, string encoding, boolean parse)
         * @see #include(Template includedTemplate)
         */
        public void Include(string name, Encoding encoding, bool parse)
        {
            Include(GetTemplateForInclusion(name, encoding, parse));
        }

        /**
         * Gets a template for inclusion; used with {@link #include(Template includedTemplate)}.
         * The advantage over simply using <code>config.getTemplate(...)</code> is that it chooses
         * the default encoding as the <code>include</code> directive does.
         *
         * @param name the name of the template, relatively to the template root directory
         * (not the to the directory of the currently executing template file!).
         * (Note that you can use {@link freemarker.cache.TemplateCache#getFullTemplatePath}
         * to convert paths to template root relative paths.)
         * @param encoding the encoding of the obtained template. If null,
         * the encoding of the Template that is currently being processed in this
         * Environment is used.
         * @param parse whether to process a parsed template or just include the
         * unparsed template source.
         */
        public Template.Template GetTemplateForInclusion(string name, Encoding encoding, bool parse)
        {
            if (encoding == null)
            {
                encoding = GetTemplate().GetEncoding();
            }
            if (encoding == null)
            {
                encoding = GetConfiguration().GetEncoding(GetLocale());
            }
            return GetConfiguration().GetTemplate(name, GetLocale(), encoding, parse);
        }

        /**
         * Processes a Template in the context of this <code>Environment</code>, including its
         * output in the <code>Environment</code>'s Writer.
         *
         * @param includedTemplate the template to process. Note that it does <em>not</em> need
         * to be a template returned by
         * {@link #getTemplateForInclusion(string name, string encoding, boolean parse)}.
         */
        public void Include(Template.Template includedTemplate)
        {
            Template.Template prevTemplate = GetTemplate();
            SetParent(includedTemplate);
            ImportMacros(includedTemplate);
            try
            {
                Visit(includedTemplate.GetRootTreeNode());
            }
            finally
            {
                SetParent(prevTemplate);
            }
        }

        /**
         * Emulates <code>import</code> directive, except that <code>name</code> must be tempate
         * root relative.
         *
         * <p>It's the same as <code>importLib(getTemplateForImporting(name), namespace)</code>.
         * But, you may want to separately call these two methods, so you can determine the source of
         * exceptions more precisely, and thus achieve more intelligent error handling.
         *
         * @see #getTemplateForImporting(string name)
         * @see #importLib(Template includedTemplate, string namespace)
         */
        public Namespace ImportLib(string name, string ns)
        {
            return ImportLib(GetTemplateForImporting(name), ns);
        }

        /**
         * Gets a template for importing; used with
         * {@link #importLib(Template importedTemplate, string namespace)}. The advantage
         * over simply using <code>config.getTemplate(...)</code> is that it chooses the encoding
         * as the <code>import</code> directive does.
         *
         * @param name the name of the template, relatively to the template root directory
         * (not the to the directory of the currently executing template file!).
         * (Note that you can use {@link freemarker.cache.TemplateCache#getFullTemplatePath}
         * to convert paths to template root relative paths.)
         */
        public Template.Template GetTemplateForImporting(string name)
        {
            return GetTemplateForInclusion(name, null, true);
        }

        /**
         * Emulates <code>import</code> directive.
         *
         * @param loadedTemplate the template to import. Note that it does <em>not</em> need
         * to be a template returned by {@link #getTemplateForImporting(string name)}.
         */

        public Namespace ImportLib(Template.Template loadedTemplate, string ns)
        {
            if (_loadedLibs == null)
            {
                _loadedLibs = new Dictionary<string, Namespace>();
            }
            string templateName = loadedTemplate.GetName();
            Namespace existingNamespace = _loadedLibs.GetValueOrDefault(templateName);
            if (existingNamespace != null)
            {
                if (ns != null)
                {
                    SetVariable(ns, existingNamespace);
                }
            }
            else
            {
                var newNamespace = new Namespace(this, loadedTemplate);
                if (ns != null)
                {
                    _currentNamespace.Put(ns, newNamespace);
                    if (_currentNamespace == _mainNamespace)
                    {
                        _globalNamespace.Put(ns, newNamespace);
                    }
                }
                Namespace prevNamespace = _currentNamespace;
                _currentNamespace = newNamespace;
                _loadedLibs.Add(templateName, _currentNamespace);
                TextWriter prevOut = _out;
                _out = NullWriter;
                try
                {
                    Include(loadedTemplate);
                }
                finally
                {
                    _out = prevOut;
                    _currentNamespace = prevNamespace;
                }
            }
            return _loadedLibs.GetValueOrDefault(templateName);
        }

        internal string RenderElementToString(TemplateElement te)
        {
            TextWriter prevOut = _out;
            try
            {
                var sw = new StringWriter();
                _out = sw;
                Visit(te);
                return sw.ToString();
            }
            finally
            {
                _out = prevOut;
            }
        }

        void ImportMacros(Template.Template template)
        {
            foreach (var macro in template.GetMacros().Values)
            {
                VisitMacroDef(macro);
            }
        }

        /**
         * @return the namespace URI registered for this prefix, or null.
         * This is based on the mappings registered in the current namespace.
         */

        public string GetNamespaceForPrefix(string prefix)
        {
            return _currentNamespace.GetTemplate().GetNamespaceForPrefix(prefix);
        }

        public string GetPrefixForNamespace(string nsUri)
        {
            return _currentNamespace.GetTemplate().GetPrefixForNamespace(nsUri);
        }

        /**
         * @return the default node namespace for the current FTL namespace
         */
        public string GetDefaultNs()
        {
            return _currentNamespace.GetTemplate().GetDefaultNs();
        }

        ///**
        // * A hook that Jython uses.
        // */
        //public object __getitem__(string key) throws TemplateModelException {
        //    return BeansWrapper.getDefaultInstance().unwrap(getVariable(key));
        //}
        ///**
        // * A hook that Jython uses.
        // */
        //public void __setitem__(string key, object o) throws TemplateException {
        //    setGlobalVariable(key, getObjectWrapper().wrap(o));
        //}

        private sealed class NumberFormatKey
        {
            private readonly string _pattern;
            private readonly CultureInfo _locale;

            internal NumberFormatKey(string pattern, CultureInfo locale)
            {
                _pattern = pattern;
                _locale = locale;
            }

            public override bool Equals(object o)
            {
                if (o is NumberFormatKey)
                {
                    var fk = (NumberFormatKey) o;
                    return fk._pattern.Equals(_pattern) && fk._locale.Equals(_locale);
                }
                return false;
            }

            public override int GetHashCode()
            {
                return _pattern.GetHashCode() ^ _locale.GetHashCode();
            }
        }

        private sealed class DateFormatKey
        {
            private readonly int _dateType;
            private readonly string _pattern;
            private readonly CultureInfo _locale;
            private readonly TimeZoneInfo _timeZone;

            internal DateFormatKey(int dateType, string pattern, CultureInfo locale, TimeZoneInfo timeZone)
            {
                _dateType = dateType;
                _pattern = pattern;
                _locale = locale;
                _timeZone = timeZone;
            }

            public override bool Equals(object o)
            {
                if (o is DateFormatKey)
                {
                    var fk = (DateFormatKey) o;
                    return _dateType == fk._dateType &&
                           fk._pattern.Equals(_pattern) &&
                           fk._locale.Equals(_locale) &&
                           fk._timeZone.Equals(_timeZone);
                }
                return false;
            }

            public override int GetHashCode()
            {
                return _dateType ^ _pattern.GetHashCode() ^ _locale.GetHashCode() ^ _timeZone.GetHashCode();
            }
        }

        public class Namespace : SimpleHash
        {
            private readonly Environment _environment;
            private readonly Template.Template _template;

            internal Namespace(Environment environment)
            {
                _environment = environment;
                _template = _environment.GetTemplate();
            }

            internal Namespace(Environment environment, Template.Template template)
            {
                _environment = environment;
                _template = template;
            }

            /**
             * @return the Template object with which this Namespace is associated.
             */

            public Template.Template GetTemplate()
            {
                return _template ?? _environment.GetTemplate();
            }
        }

        internal static readonly TextWriter NullWriter = new NullWriterImpl();

        private class NullWriterImpl : TextWriter
        {
            public override Encoding Encoding
            {
                get { return Encoding.Default; }
            }
        }

        private static readonly TextWriter EmptyBodyWriter = new EmptyBodyWriterImpl();
        
        private class EmptyBodyWriterImpl : TextWriter
        {
            public override Encoding Encoding
            {
                get { return Encoding.Default; }
            }

            public override void Write(char[] buffer, int index, int count)
            {
                if (count > 0)
                {
                    throw new IOException(
                        "This transform does not allow nested content.");
                }
            }

            public override void Close()
            {
            }

            public override void Flush()
            {
            }
        }
    }
}